/******************************************************************************
 * @file     spi.c
 * @brief    Bootloader中spi外设相关函数
 * @author   Ruilist
 * @date     2025-08-15
 * @version  V1.0.0
 * @copyright Copyright (c) 2025
 *
 * @note     1 tab = 4 space
 *
 * @par 日志:
 * - V1.0.0 2025-08-15 Ruilist
 *****************************************************************************/

//******************************** Includes *********************************//

#include "spi.h"

//******************************** Includes *********************************//

//******************************** Defines **********************************//

extern volatile uint32_t SysTickUptime;

//******************************** Defines **********************************//

//******************************** Declaring ********************************//

/**
 * @brief   spi的cs接口初始化，CS PA4
 *
 * @param   void
 *
 * @return  void
 *
 */
void CS_IO_Init( void )
{
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_OUT;  // 输出
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init( GPIOA, &GPIO_InitStructure );
}

/**
 * @brief   spi的接口初始化
 *
 * @param   void
 *
 * @return  void
 *
 */
void SPI1_Init( void )
{
    CS_IO_Init();

    GPIO_WriteBit( F_CS_GPIO_Port, F_CS_Pin, Bit_SET );

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_SPI1, ENABLE );   // 使能SPI1时钟
    RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOA, ENABLE );  // 使能GPIOA时钟

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;   // 模式需要设置为复用
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;  // 设置为推挽输出
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init( GPIOA, &GPIO_InitStructure );

    GPIO_PinAFConfig( GPIOA, GPIO_PinSource5, GPIO_AF_SPI1 );  // PA5复用为SPI1
    GPIO_PinAFConfig( GPIOA, GPIO_PinSource6, GPIO_AF_SPI1 );  // PA6复用为SPI1
    GPIO_PinAFConfig( GPIOA, GPIO_PinSource7, GPIO_AF_SPI1 );  // PA7复用为SPI1

    RCC_APB2PeriphResetCmd( RCC_APB2Periph_SPI1, ENABLE );   // 复位SPI1
    RCC_APB2PeriphResetCmd( RCC_APB2Periph_SPI1, DISABLE );  // 停止复位SPI1

    SPI_InitTypeDef SPI_InitStructure;
    SPI_InitStructure.SPI_Direction         = SPI_Direction_2Lines_FullDuplex;  // SPI双向全双工
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;        // 波特率预分频值256
    SPI_InitStructure.SPI_CPHA              = SPI_CPHA_1Edge;                   // 串行时钟进行奇次采样
    SPI_InitStructure.SPI_CPOL              = SPI_CPOL_Low;                     // 串行时钟空闲状态为低电平
    SPI_InitStructure.SPI_CRCPolynomial     = 7;                                // CRC值计算多项式
    SPI_InitStructure.SPI_DataSize          = SPI_DataSize_8b;                  // SPI数据帧8位
    SPI_InitStructure.SPI_FirstBit          = SPI_FirstBit_MSB;                 // 数据传输高位在前
    SPI_InitStructure.SPI_Mode              = SPI_Mode_Master;                  // SPI主模式，即时钟时序是由主机SCK提供的
    SPI_InitStructure.SPI_NSS               = SPI_NSS_Soft;                     // NSS信号由软件管理
    SPI_Init( SPI1, &SPI_InitStructure );

    SPI_Cmd( SPI1, ENABLE );  // 使能SPI时钟

    SPI1_ReadWriteByte( 0xff );  // 启动传输
}

/**
 * @brief   spi的速度设置
 *
 * @param   uint8_t SPI_BaudRate_Prescaler
 *
 * @return  void
 *
 */
void SPI1_SetSpeed( uint8_t SPI_BaudRate_Prescaler )
{
    assert_param( IS_SPI_BAUDRATE_PRESCALER( SPI_BaudRatePrescaler ) );
    // 判断有效性 assert_param是STM32库函数判断有效性的函数，如果波特率预分频值不在上述的范围内，就报错
    // 位带操作主要分两步，第一步将所要操作的寄存器的某一位清零  第二步将寄存器清零的相关位写入所需的值
    SPI1->CR1 &= 0xFFC7;                  // 0xFFC7对应于 1111 1111 1100 0111，相与也就表示将CR1寄存器的3-5位清零
    SPI1->CR1 |= SPI_BaudRate_Prescaler;  // 设置SPI速度，上一步清零，这一步将所需位写入相关的值
    SPI_Cmd( SPI1, ENABLE );              // 使能SPI1
}

/**
 * @brief   spi读写一个字节
 *
 * @param   uint8_t WriteData 写入的数据
 *
 * @return  uint8_t
 * @retval  byte 接收到的字节
 *
 */
uint8_t SPI1_ReadWriteByte( uint8_t WriteData )
{
    while ( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE ) == RESET )
    {
    }
    // TXE为状态位用来判断缓存寄存器是否为空
    // TXE若为0，则表示缓存寄存器非空
    // TXE若为1，则表示缓存寄存器空
    // 只要跳出while循环，意味着TXE=1，缓存寄存器为空，可以写入下一个数值了
    SPI_I2S_SendData( SPI1, WriteData );  // 通过SPI1发送一个字节的数据
    while ( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_RXNE ) == RESET )
    {
    }
    // RXNE是用来判断接收缓存区是否为空的状态位，其状态位标志和TXE相同
    return SPI_I2S_ReceiveData( SPI1 );  // 返回通过SPI接收的数据
}

/**
 * @brief   spi连续写入数据
 *
 * @param   uint8_t *WriteData 写入的数据的地址
 * @param   uint16_t dataSize  写入的数据的大小
 * @param   uint32_t timeout   超时时间
 *
 * @return  uint8_t
 * @retval  0 写入失败
 * @retval  1 写入成功
 *
 */
uint8_t SPI1_WriteByte( uint8_t *WriteData, uint16_t dataSize, uint32_t timeout )
{
    uint32_t time         = timeout;
    uint32_t current_time = SysTickUptime;
    uint16_t txsize       = dataSize;
    uint16_t rxsize       = dataSize;
    uint8_t *pTxBuffPtr   = WriteData;
    uint8_t  txflow       = 1u;
    while ( ( txsize > 0 ) || ( rxsize > 0 ) )
    {
        if ( ( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE ) != RESET ) && ( txsize > 0 ) && ( txflow == 1 ) )
        {
            SPI_I2S_SendData( SPI1, *pTxBuffPtr );
            pTxBuffPtr += sizeof( uint8_t );
            txsize--;
            txflow = 0;
        }
        if ( ( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_RXNE ) != RESET ) && ( rxsize > 0 ) && ( txflow == 0 ) )
        {
            SPI_I2S_ReceiveData( SPI1 );
            rxsize--;
            txflow = 1;
        }
        if ( ( SysTickUptime - current_time ) >= time )
        {
            SPI_I2S_ClearFlag( SPI1, SPI_I2S_FLAG_OVR );
            return 0;
        }
    }
    SPI_I2S_ClearFlag( SPI1, SPI_I2S_FLAG_OVR );
    return 1;
}

/**
 * @brief   spi连续读出数据
 *
 * @param   uint8_t *WriteData 读出的数据的地址
 * @param   uint16_t dataSize  读出的数据的大小
 * @param   uint32_t timeout   超时时间
 *
 * @return  uint8_t
 * @retval  0 读出失败
 * @retval  1 读出成功
 *
 */
uint8_t SPI1_ReadByte( uint8_t *ReadData, uint16_t dataSize, uint32_t timeout )
{
    uint32_t time         = timeout;
    uint32_t current_time = SysTickUptime;
    uint16_t txsize       = dataSize;
    uint16_t rxsize       = dataSize;
    uint8_t *pTxBuffPtr   = ReadData;
    uint8_t *pRxBuffPtr   = ReadData;
    uint8_t  txflow       = 1u;
    while ( ( txsize > 0 ) || ( rxsize > 0 ) )
    {
        if ( ( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE ) != RESET ) && ( txsize > 0 ) && ( txflow == 1 ) )
        {
            SPI_I2S_SendData( SPI1, *pTxBuffPtr );
            pTxBuffPtr += sizeof( uint8_t );
            txsize--;
            txflow = 0;
        }
        if ( ( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_RXNE ) != RESET ) && ( rxsize > 0 ) && ( txflow == 0 ) )
        {
            *pRxBuffPtr = SPI_I2S_ReceiveData( SPI1 );
            pRxBuffPtr += sizeof( uint8_t );
            rxsize--;
            txflow = 1;
        }
        if ( ( SysTickUptime - current_time ) >= time )
        {
            return 0;
        }
    }
    return 1;
}

//******************************** Declaring ********************************//
